﻿Imports System.IO
Imports System.Net.Http
Imports System.Text
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq

Public Class Form1

    Private _sessionsPath As String = Path.Combine(Application.StartupPath, "Sessions")
    Private _currentSessionName As String = ""
    Private _allSessions As New List(Of ChatSessionInfo)
    ' ====== 打字机效果变量 ======
    Private _fullAnswer As String = ""
    Private _typingIndex As Integer = 0
    Private _isTyping As Boolean = False
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        If Not Directory.Exists(_sessionsPath) Then
            Directory.CreateDirectory(_sessionsPath)
        End If

        LoadSessionList()
    End Sub

    Private Sub LoadSessionList()
        _allSessions.Clear()

        For Each file As String In Directory.GetFiles(_sessionsPath, "*.json")
            Dim fileName As String = Path.GetFileName(file)
            Dim sessionName As String = Path.GetFileNameWithoutExtension(file)
           ' Dim lastWriteTime As DateTime = File.GetLastWriteTime(file) ' 正确用法
            Dim lastWriteTime As DateTime = System.IO.File.GetLastWriteTime(file)
            _allSessions.Add(New ChatSessionInfo With {
                .SessionName = sessionName,
                .FileName = fileName,
                .LastModified = lastWriteTime
            })
        Next

        ' 按修改时间倒序排序
        _allSessions = _allSessions.OrderByDescending(Function(s) s.LastModified).ToList()

        ListBox1.DataSource = Nothing
        ListBox1.DataSource = _allSessions
    End Sub

    ''' <summary>
    ''' 发送消息按钮点击
    ''' </summary>
    Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim question As String = TextBox1.Text.Trim()
        If String.IsNullOrEmpty(question) Then
            MessageBox.Show("请输入你的问题！", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Return
        End If

        ' 禁用控件
        Button1.Enabled = False
        TextBox1.Enabled = False

        Try
            ' 1️⃣ 添加用户问题（老版风格）
            AppendToChat($"👤 您:{Environment.NewLine}{question}{Environment.NewLine}")

            ' 2️⃣ 准备 AI 回答区域
            AppendToChat("🤖 Qwen:" & Environment.NewLine)

            ' 3️⃣ 获取回答
            Dim answer As String = Await GetQwenResponse(question)

            ' 4️⃣ 启动打字机效果
            Await SimulateTyping(answer)

            ' 5️⃣ 添加分隔线
            Dim separator As String = New String("─"c, 50)
            AppendToChat($"{Environment.NewLine}{separator}{Environment.NewLine}")

            ' 6️⃣ 保存当前会话（追加最新问答）
            SaveCurrentSession(question, answer)

        Catch ex As Exception
            AppendToChat($"❌ 错误: {ex.Message}{Environment.NewLine}")
        Finally
            ' 恢复控件
            Button1.Enabled = True
            TextBox1.Enabled = True
            TextBox1.Clear()
            TextBox1.Focus()
        End Try
    End Sub
    ''' <summary>
    ''' 安全地向 RichTextBox 追加文本（跨线程）
    ''' </summary>
    Private Sub AppendToChat(text As String)

        If RichTextBox1.InvokeRequired Then
            RichTextBox1.Invoke(Sub() AppendToChat(text))
        Else
'            RichTextBox1.AppendText(text)
'            RichTextBox1.SelectionStart = RichTextBox1.TextLength
'            RichTextBox1.ScrollToCaret()
            RichTextBox1.AppendText(text)
            ' 不在此处 ScrollToCaret()，由 SimulateTyping 控制
        End If
    End Sub


    ''' <summary>
    ''' 模拟打字机效果，支持中途停止，减少抖动
    ''' </summary>
    Private Async Function SimulateTyping(answer As String) As Task
        _fullAnswer = answer
        _typingIndex = 0
        _isTyping = True

        ' 启用停止按钮
        ButtonStop.Invoke(Sub() ButtonStop.Enabled = True)

        Try
            While _isTyping AndAlso _typingIndex < _fullAnswer.Length
                Dim c As Char = _fullAnswer(_typingIndex)

                ' 直接追加字符（不立即滚动）
                AppendToChat(c.ToString())

                _typingIndex += 1

                ' 控制速度
                Await Task.Delay(30)

                ' === 关键优化：只在接近底部时才滚动 ===
                ' 避免每打一个字都滚动，造成抖动
                If ShouldAutoScroll() Then
                    RichTextBox1.Invoke(Sub()
                        RichTextBox1.SelectionStart = RichTextBox1.TextLength
                        RichTextBox1.ScrollToCaret()
                    End Sub)
                End If
            End While
        Finally
            ' 确保结束时禁用按钮
            ButtonStop.Invoke(Sub() ButtonStop.Enabled = False)
            _isTyping = False
        End Try
    End Function
    ''' <summary>
    ''' 判断当前是否应自动滚动到底部（防抖动）
    ''' </summary>
    Private Function ShouldAutoScroll() As Boolean
        Try
            ' 获取最后一行的行号
            Dim totalLines As Integer = RichTextBox1.GetLineFromCharIndex(RichTextBox1.TextLength)
            ' 获取当前光标所在行
            Dim currentLine As Integer = RichTextBox1.GetLineFromCharIndex(RichTextBox1.SelectionStart)

            ' 计算可视区域大约有多少行
            Dim fontHeight As Integer = TextRenderer.MeasureText("X", RichTextBox1.Font).Height
            Dim visibleLines As Integer = RichTextBox1.ClientSize.Height \ fontHeight

            ' 如果总行数还没超过可视区域，就滚动
            If totalLines <= visibleLines Then
                Return True
            End If

            ' 否则：只有当当前行接近底部时才继续滚动（防止滚动到中间时强制拉下去）
            Return (totalLines - currentLine) <= visibleLines + 2
        Catch
            ' 出错则默认滚动（安全兜底）
            Return True
        End Try
    End Function
    ''' <summary>
    ''' 停止 AI 输出打字效果
    ''' </summary>
    Private Sub ButtonStop_Click(sender As Object, e As EventArgs) Handles ButtonStop.Click
        _isTyping = False
        ButtonStop.Enabled = False

        ' 可选：显示已输出的部分 + [已停止]
        AppendToChat(" [输出已停止]{Environment.NewLine}")
    End Sub

    Private Sub AppendChatBubble(role As String, content As String)
        RichTextBox1.SelectionFont = New Font(RichTextBox1.Font, If(role = "user", FontStyle.Bold, FontStyle.Italic))
        RichTextBox1.SelectionColor = If(role = "user", Color.Blue, Color.DarkGreen)
        RichTextBox1.AppendText($"{role}: {content}{Environment.NewLine}{Environment.NewLine}")
        RichTextBox1.ScrollToCaret()
    End Sub

    Private Async Function GetQwenResponse(prompt As String) As Task(Of String)
        Using client As New HttpClient()
            Dim apiKey As String = "sk-d34a337bc2844da59ced5a5911e8660e" ' 替换为你自己的
            Dim url As String = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"

            client.DefaultRequestHeaders.Authorization = New Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiKey)

            Dim jsonContent As String = $"
            {{
                ""model"": ""qwen-turbo"",
                ""input"": {{
                    ""messages"": [
                        {{ ""role"": ""user"", ""content"": ""{prompt}"" }}
                    ]
                }}
            }}"

            Dim content As New StringContent(jsonContent, Encoding.UTF8, "application/json")

            Dim response As HttpResponseMessage = Await client.PostAsync(url, content)
            Dim resultJson As String = Await response.Content.ReadAsStringAsync()

            If response.IsSuccessStatusCode Then
                Dim jObj As JObject = JObject.Parse(resultJson)
                Return jObj("output")("text").ToString().Trim()
            Else
                Return $"API 错误 [{response.StatusCode}]: {resultJson}"
            End If
        End Using
    End Function

    Private Sub SaveCurrentSession(userInput As String, aiResponse As String)
        Dim fileName As String = _currentSessionName
        If String.IsNullOrEmpty(fileName) Then
            fileName = $"session_{DateTime.Now:yyyyMMdd_HHmmss}.json"
            _currentSessionName = fileName
        End If

        Dim filePath As String = Path.Combine(_sessionsPath, fileName)

        ' 构建消息列表（简化：只保存最新一条）
        Dim messages As New List(Of Object) From {
            New With {.role = "user", .content = userInput},
            New With {.role = "assistant", .content = aiResponse}
        }

        Dim data As New With {messages}
        Dim json As String = JsonConvert.SerializeObject(data, Formatting.Indented)
        File.WriteAllText(filePath, json, Encoding.UTF8)

        ' 刷新会话列表
        LoadSessionList()
    End Sub

    Private Async Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
        Dim session As ChatSessionInfo = CType(ListBox1.SelectedItem, ChatSessionInfo)
        If session IsNot Nothing Then
            Await LoadSession(session)
        End If
    End Sub

    Public Async Function LoadSession(session As ChatSessionInfo) As Task
        Dim filePath As String = Path.Combine(_sessionsPath, session.FileName)
        If Not File.Exists(filePath) Then
            MessageBox.Show("会话文件不存在。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        Try
            ' 使用同步读取（.NET 4.7.2 支持 async file ops，但为简单起见）
            Dim json As String = File.ReadAllText(filePath, Encoding.UTF8)
            Dim jObj As JObject = JObject.Parse(json)
            Dim messages As JToken = jObj("messages")

            RichTextBox1.Clear()

            If messages IsNot Nothing Then
                For Each msg As JToken In messages
                    Dim role As String = msg("role").ToString()
                    Dim content As String = msg("content").ToString()
                    AppendChatBubble(role, content)
                Next
            End If

            _currentSessionName = session.FileName
        Catch ex As Exception
            MessageBox.Show($"加载会话失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Function

    Private Sub ButtonNewSession_Click(sender As Object, e As EventArgs) Handles ButtonNewSession.Click
        _currentSessionName = ""
        RichTextBox1.Clear()
        TextBox1.Clear()
    End Sub
'
    Private Sub ButtonRefresh_Click(sender As Object, e As EventArgs) Handles ButtonRefresh.Click
        LoadSessionList()
    End Sub
    ''' <summary>
    ''' 按 Enter 发送
    ''' </summary>
    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown
        If e.KeyCode = Keys.Enter Then
            Button1.PerformClick()
            e.SuppressKeyPress = True
        End If
    End Sub
End Class